import ctypes
from abc import update_abstractmethods
from typing import Union

import cv2
import numpy as np

from .camera import EmccdCamera, Status


class EmccdCameraFake(EmccdCamera):
    # lifecycle

    def __init__(self):
        super().__init__()
        self.w = self.h = 512
        self.i = 0

    # information

    def get_head_model(self) -> str:
        return "Fake EMCCD"

    def get_detector_size(self) -> tuple[int, int]:
        return self.w, self.h

    # temperature

    def get_temperature_range(self) -> tuple[int, int]:
        return -120, 20

    def get_temperature(self) -> int:
        return 0

    # pre-amp

    def get_pre_amp_gains_num(self) -> int:
        return 1

    # baseline clamp

    @property
    def support_baseline_clamp(self) -> bool:
        return True

    @property
    def support_baseline_offset(self) -> bool:
        return True

    # vertical shift speeds

    def get_vs_speeds_num(self) -> int:
        return 1

    def get_vs_speed(self, vs_speed_index: int) -> float:
        """  vertical shift speed (ms/pixel) """
        return 1.0

    def get_fastest_recommended_vs_speed(self) -> tuple[int, float]:
        return 1, 1.0

    # ad channels & horizontal shift speeds

    def get_ad_channels_num(self) -> int:
        return 1

    def get_hs_speeds_num(self, ad_channel_index: int, amplification: int) -> int:
        return 1

    def get_hs_speed(self, ad_channel_index: int, amplification: int, hs_speed_index: int) -> float:
        """ Horizontal shift speed (MHz) """
        return 1.0

    # acquisition mode & exposure & cycle

    def get_acquisition_times(self) -> tuple[float, float, float]:
        return 0.001, 0.001, 0.05

    # acquisition control

    def get_status(self) -> Status:
        return Status.IDLE

    # data

    def get_acquired_data(self, size: int, buffer: Union[ctypes.POINTER, ctypes.Array]):
        self.get_latest_image(size, buffer)

    def get_latest_image(self, size: int, buffer: Union[ctypes.POINTER, ctypes.Array]):
        img = generate_img(self.i)

        buffer = np.frombuffer(buffer, dtype=np.uint32).reshape((512, 512))
        buffer[:, :] = img.astype(np.int32) * 4
        self.i += 1


# implement all abstract methods

for abc_func_name in dir(EmccdCameraFake):
    if abc_func_name.startswith("__"):
        continue
    abc_func = getattr(EmccdCameraFake, abc_func_name)
    if not getattr(abc_func, '__isabstractmethod__', None):
        continue


    def _build_fake_func(name: str):
        def _fake_func(_, *args, **kwargs):
            args_str = (str(arg) for arg in args)
            kwargs_str = ((str(k) + '=' + str(v)) for k, v in kwargs.items())
            print(f"{name}({','.join((*args_str, *kwargs_str))})")

        _fake_func.__name__ = name
        return _fake_func


    setattr(EmccdCameraFake, abc_func_name, _build_fake_func(abc_func_name))
update_abstractmethods(EmccdCameraFake)


# utils

def generate_img(i: int) -> np.ndarray:
    img = np.zeros((512, 512), dtype=np.uint8)
    for p in range(8):
        d = 512 // 8
        f = p + i % d
        x = f + p * d
        cv2.line(img, (x, 0), (x, 512), 255, 1)
        cv2.line(img, (0, x), (512, x), 255, 1)
    cv2.putText(img, f"frame {i}", (128, 256), cv2.FONT_HERSHEY_SIMPLEX, 1, 255)
    return img
